import type { RedisClient } from "./client";

export class CoreCommand {
    constructor(client: RedisClient) {
        this.client = client;
    }

    private readonly client: RedisClient;

    // return 1 for success,0 for fail
    async copy(srcKey: string, destKey: string): Promise<number> {
        const res = await this.client.exec("COPY", srcKey, destKey);
        return res as number;
    }

    // return The number of keys that were removed.
    async del(...keyList: string[]): Promise<number> {
        const res = await this.client.exec("DEL", ...keyList);
        return res as number;
    }

    // return the serialized value.
    async dump(key: string): Promise<string> {
        const res = await this.client.exec("DUMP", key);
        return res as string;
    }

    // specifically the number of keys that exist from those specified as arguments.
    async exists(...keyList: string[]): Promise<number> {
        const res = await this.client.exec("EXISTS", ...keyList);
        return res as number;
    }

    // return 1 for set,0 for not set
    async expire(key: string, seconds: number, option: "" | "NX" | "XX" | "GT" | "LT" = ""): Promise<number> {
        const args = ["EXPIRE", key, seconds.toFixed(0)]
        if (option !== "") {
            args.push(option);
        }
        const res = await this.client.exec(...args);
        return res as number;
    }

    // return 1 for set,0 for not set
    async expireAt(key: string, unixTimeStamp: number, option: "" | "NX" | "XX" | "GT" | "LT" = ""): Promise<number> {
        const args = ["EXPIREAT", key, unixTimeStamp.toFixed(0)]
        if (option !== "") {
            args.push(option);
        }
        const res = await this.client.exec(...args);
        return res as number;
    }

    // return Expiration Unix timestamp in seconds, or a negative value in order to signal an error
    async expireTime(key: string): Promise<number> {
        const res = await this.client.exec("EXPIRETIME", key);
        return res as number;
    }

    // return list of keys matching pattern.
    async keys(pattern: string): Promise<string[]> {
        const res = await this.client.exec("KEYS", pattern);
        return res as string[];
    }

    // The command returns OK on success, or NOKEY if no keys were found in the source instance.
    async migrate(host: string, port: number, dbIndex: number, timeout: number, keyList: string[],
        username: string = "", password: string = "", copy: boolean = false, replace: boolean = false): Promise<string> {
        const args = ["MIGRATE", host, port.toFixed(0), "", dbIndex.toFixed(0), timeout.toFixed(0)];
        if (copy) {
            args.push("COPY");
        }
        if (replace) {
            args.push("REPLACE");
        }
        if (username === "" && password !== "") {
            args.push("AUTH", password);
        } else if (username !== "" && password !== "") {
            args.push("AUTH2", username, password);
        }
        args.push("KEYS", ...keyList);
        const res = await this.client.exec(...args);
        return res as string;
    }

    // return 1 for key moved,0 for not moved
    async move(key: string, dbIndex: number): Promise<number> {
        const res = await this.client.exec("MOVE", key, dbIndex.toFixed(0));
        return res as number;
    }

    // return the encoding of the object, or nil if the key doesn't exist
    async objectEncoding(key: string): Promise<string | null> {
        const res = await this.client.exec("OBJECT", "ENCODING", key);
        if (res == null) {
            return null;
        }
        return res as string;
    }

    // return access count value
    async objectFreq(key: string): Promise<number> {
        const res = await this.client.exec("OBJECT", "FREQ", key);
        return res as number;
    }

    // return The idle time in seconds
    async objectIdleTime(key: string): Promise<number> {
        const res = await this.client.exec("OBJECT", "IDLETIME", key);
        return res as number;
    }

    // return The number of references.
    async objectRefCount(key: string): Promise<number> {
        const res = await this.client.exec("OBJECT", "REFCOUNT", key);
        return res as number;
    }

    // return 1 from remove timeout,0 for not
    async persist(key: string): Promise<number> {
        const res = await this.client.exec("PERSIST", key);
        return res as number;
    }

    // return 1 for set timeout,0 for not
    async pexpire(key: string, milliSeconds: number, option: "" | "NX" | "XX" | "GT" | "LT" = ""): Promise<number> {
        const args = ["PEXPIRE", key, milliSeconds.toFixed(0)];
        if (option !== "") {
            args.push(option);
        }
        const res = await this.client.exec(...args);
        return res as number;
    }

    // return 1 for set timeout,0 for not
    async pexpireAt(key: string, unixMilliSeconds: number, option: "" | "NX" | "XX" | "GT" | "LT" = ""): Promise<number> {
        const args = ["PEXPIREAT", key, unixMilliSeconds.toFixed(0)];
        if (option !== "") {
            args.push(option);
        }
        const res = await this.client.exec(...args);
        return res as number;
    }

    // return Expiration Unix timestamp in milliseconds, or a negative value in order to signal an error
    async pexpireTime(key: string): Promise<number> {
        const res = await this.client.exec("PEXPIRETIME", key);
        return res as number;
    }

    // return TTL in milliseconds, or a negative value in order to signal an error
    async pttl(key: string): Promise<number> {
        const res = await this.client.exec("PTTL", key);
        return res as number;
    }

    // return the random key, or nil when the database is empty.
    async randomKey(): Promise<string | null> {
        const res = await this.client.exec("RANDOMKEY");
        if (res === null) {
            return null;
        }
        return res as string;
    }

    async rename(key: string, newKey: string): Promise<string> {
        const res = await this.client.exec("RENAME", key, newKey);
        return res as string;
    }

    // return 1 if key was renamed to newkey,0 if newkey already exists.
    async renameNx(key: string, newKey: string): Promise<number> {
        const res = await this.client.exec("RENAMENX", key, newKey);
        return res as number;
    }

    async restore(key: string, ttl: number, serValue: string, replace: boolean = false, absTtl: boolean = false, idelTime: number = 0, freq: number = 0): Promise<string> {
        const args = ["RESTORE", key, ttl.toFixed(0), serValue];
        if (replace) {
            args.push("REPLACE");
        }
        if (absTtl) {
            args.push("ABSTTL");
        }
        if (idelTime > 0) {
            args.push(idelTime.toFixed(0));
        }
        if (freq > 0) {
            args.push(freq.toFixed(0));
        }
        const res = await this.client.exec(...args);
        return res as string;
    }

    async scan(cursor: number, pattern: string = "", count: number = 0, scanType: "" | "string" | "list" | "set" | "zset" | "hash" = ""): Promise<unknown> {
        const args = ["SCAN", cursor.toFixed(0)];
        if (pattern !== "") {
            args.push("MATCH", pattern);
        }
        if (count !== 0) {
            args.push("COUNT", count.toFixed());
        }
        if (scanType !== "") {
            args.push("TYPE", scanType);
        }
        const res = await this.client.exec(...args);
        return res;
    }

    async sort(key: string, by: string = "", offset: number = 0, limit: number = 0, getList: string[] = [], sort: "" | "ASC" | "DESC" = "", alpha: boolean = false, storeKey: string = ""): Promise<unknown> {
        const args = ["SORT", key];
        if (by !== "") {
            args.push("BY", by);
        }
        if (limit !== 0) {
            args.push("LIMIT", offset.toFixed(0), limit.toFixed(0));
        }
        for (const getStr of getList) {
            args.push("GET", getStr);
        }
        if (sort !== "") {
            args.push(sort);
        }
        if (alpha) {
            args.push("ALPHA");
        }
        if (storeKey !== "") {
            args.push("STORE", storeKey);
        }
        const res = await this.client.exec(...args);
        return res;
    }

    async sortRo(key: string, by: string = "", offset: number = 0, limit: number = 0, getList: string[] = [], sort: "" | "ASC" | "DESC" = "", alpha: boolean = false): Promise<unknown> {
        const args = ["SORT_RO", key];
        if (by !== "") {
            args.push("BY", by);
        }
        if (limit !== 0) {
            args.push("LIMIT", offset.toFixed(0), limit.toFixed(0));
        }
        for (const getStr of getList) {
            args.push("GET", getStr);
        }
        if (sort !== "") {
            args.push(sort);
        }
        if (alpha) {
            args.push("ALPHA");
        }
        const res = await this.client.exec(...args);
        return res;
    }

    // return The number of keys that were touched.
    async touch(keyList: string[]): Promise<number> {
        const res = await this.client.exec("TOUCH", ...keyList);
        return res as number;
    }

    // return TTL in seconds, or a negative value in order to signal an error
    async ttl(key: string): Promise<number> {
        const res = await this.client.exec("TTL", key);
        return res as number;
    }

    // return type of key, or none when key does not exist.
    async getType(key: string): Promise<null | string> {
        const res = await this.client.exec("TYPE", key);
        if (res == null) {
            return null;
        }
        return res as string;
    }

    // return The number of keys that were unlinked.
    async unlink(keyList:string[]):Promise<number> {
        const res = await this.client.exec("UNLINK",...keyList);
        return res as number;
    }
}